/*
	This file is part of Posixcpp library.

	Posixcpp library is free software: you can redistribute it and/or
	modify it under the terms of the GNU Lesser General Public License
	as published by the Free Software Foundation, either version 3 of
	the License, or (at your option) any later version.

	Posixcpp library is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU LesserGeneral Public
	License along with Posixcpp library. If not, see
	<http://www.gnu.org/licenses/>.
 */

/**
 * @file thread.cpp
 * @brief Thread class implementation
 */

#include "posix_threads_thread.hpp"
#include "posixcpp.hpp"

#include <unistd.h>
#include <sched.h>
#include <sys/syscall.h>

#include <cassert>

using posix::threads::Thread;
using namespace posix::threads;
using namespace posixcpp;

Thread::Thread( ) :
ActiveObject((void*)NULL),
m_attributes(NULL) {
    
    m_function = _threadStart;
    m_functionParameter = this;
}

Thread::Thread( pthread_t & handle ) :
ActiveObject((void*)NULL),
m_handle( handle ),
m_function( NULL ),
m_functionParameter( NULL ),
m_attributes(NULL) {

}

Thread::Thread(
  ThreadFunction_t function,
  void * param ) :
ActiveObject((void*)NULL),
m_attributes( NULL ) {
    
	m_function = function;
	m_functionParameter = param;
}

Thread::~Thread( ) {

}


Thread Thread::self( ) {
	pthread_t self = pthread_self();
	return Thread(self);
}

void Thread::start( ) {
	const pthread_attr_t * attrs;
	if (m_attributes != NULL)
		attrs = &m_attributes->handle();
	else
		attrs = NULL;
	
	POSIX_CALL_ASSERT_X(
				(errno = pthread_create( &m_handle,
										 attrs,
										 m_function,
										 m_functionParameter)) == 0,
					"Unable to create thread");
}

void * Thread::join( ) {
	void * result = NULL;

	POSIX_CALL_ASSERT_X((errno = pthread_join(m_handle, &result)) == 0,
					"Unable to join thread");
	return result;
}

ActiveObject::ExecutionsRes_t Thread::waitFinish( ) {
    return this->join();
}

void Thread::cancel( ) {
	POSIX_CALL_ASSERT_X((errno = pthread_cancel(m_handle)) == 0,
					"Unable to cancel thread");
}

void Thread::detach( ) {
	POSIX_CALL_ASSERT_X((errno = pthread_detach(m_handle)) == 0,
					"Unable to cancel thread");
}

void Thread::testCancel( ) {
	pthread_testcancel();
}

void Thread::yield( ) {
	POSIX_CALL_ASSERT_X((errno = pthread_yield()) == 0,
					"Unable to yield thread execution");
}

void Thread::exit( void * retVal ) {
	pthread_exit(retVal);
}

void * Thread::_threadStart( void* args ) {
	Thread *instance = reinterpret_cast<Thread*>(args);
	assert(instance != NULL);

    instance->run();
    
	return instance->result().pointer;
}

SchedulerPolicy_t Thread::schedPolicy( ) {
	int res;
	sched_param temp;

	POSIX_CALL_ASSERT_X((errno = pthread_getschedparam(this->m_handle,
													&res, &temp)) == 0,
					"Unable to get scheduler policy");

	return SchedulerPolicy_t(res);
}

void Thread::setSchedPolicy( SchedulerPolicy_t newVal ) {
	sched_param temp;
	temp.sched_priority = sched_get_priority_min(int(newVal));

	POSIX_CALL_ASSERT_X((errno = pthread_setschedparam(this->m_handle,
													int(newVal), &temp)) == 0,
					"Unable to get scheduler policy");
}

void Thread::setSchedParam( SchedulerParameters_t newVal ) {
	sched_param temp;
	temp.sched_priority = newVal.priority;

	POSIX_CALL_ASSERT_X((errno = pthread_setschedparam(this->m_handle,
			int(newVal.policy), &temp)) == 0,
					"Unable to set scheduler parameters");
}

SchedulerParameters_t Thread::schedParam( ) {
	SchedulerParameters_t result;
	sched_param prm;
	
	POSIX_CALL_ASSERT_X((errno = pthread_getschedparam(this->m_handle,
			reinterpret_cast<int*>(&result.policy), &prm)) == 0,
					"Unable to get scheduler parameters");
	result.priority = prm.__sched_priority;
	return result;
}

Thread::Attributes::Attributes( ) {
	POSIX_CALL_ASSERT_X((errno = pthread_attr_init(&m_handle)) == 0,
					"Unable to initialize thread attributes");
}

Thread::Attributes::~Attributes( ) {
	POSIX_CALL_ASSERT_X((errno = pthread_attr_destroy(&m_handle)) == 0,
					"Unable to destroy thread attributes");
}

SchedulerPolicy_t Thread::Attributes::schedPolicy( ) const {
	int result;

	POSIX_CALL_ASSERT_X((errno = pthread_attr_getschedpolicy(&m_handle,
															&result)) == 0,
					"Unable to get scheduler policy");

	return SchedulerPolicy_t(result);
}

void Thread::Attributes::setSchedPolicy( SchedulerPolicy_t newVal ) {
	POSIX_CALL_ASSERT_X((errno = pthread_attr_setschedpolicy(&m_handle,
															int(newVal))) == 0,
					"Unable to set scheduler policy");
}

int Thread::Attributes::schedParam( ) const {
	sched_param result;

	POSIX_CALL_ASSERT_X((errno = pthread_attr_getschedparam( &m_handle,
															&result)) == 0,
					"Unable to get scheduler parameters for attributes");

	return result.__sched_priority;
}

void Thread::Attributes::setSchedParam( int priority ) {
	sched_param prm = { .__sched_priority = priority };
	
	POSIX_CALL_ASSERT_X( (errno = pthread_attr_setschedparam(&m_handle,
										                   &prm) == 0),
					"Unable to set scheduler policy");
}

SchedulerInheritance_t Thread::Attributes::inheritSched( ) const {
	int result;
	
	POSIX_CALL_ASSERT_X( (errno = pthread_attr_getinheritsched(&m_handle,
															 &result) == 0),
					"Unable to get scheduler inheritance mode");
	
	return SchedulerInheritance_t(result);
}

void Thread::Attributes::setInheritSched( SchedulerInheritance_t newVal ) {
	POSIX_CALL_ASSERT_X( (errno = pthread_attr_setinheritsched(&m_handle,
															int(newVal)) == 0),
					"Unable to set scheduler inheritance mode");
}

CancelState_t Thread::cancelState( ) {
	CancelState_t oldState, newState = CancelState_t::ENABLE;

	POSIX_CALL_ASSERT_X((errno = pthread_setcancelstate(int(newState), (int*) &oldState)) == 0,
					"Unable to set cancel state");
	if (oldState != newState)
	{
		POSIX_CALL_ASSERT_X((errno = pthread_setcancelstate(int(oldState), NULL)) == 0,
						"Unable to set cancel state");
	}
	return oldState;
}

void Thread::setCancelState( CancelState_t newState ) {
	CancelState_t oldState;

	POSIX_CALL_ASSERT_X((errno = pthread_setcancelstate(int(newState), (int*) &oldState)) == 0,
					"Unable to set cancel state");
}

CancelType_t Thread::cancelType( ) {
	CancelType_t oldType, newType = CancelType_t::DEFERRED;

	POSIX_CALL_ASSERT_X((errno = pthread_setcanceltype(int(newType), (int*) &oldType)) == 0,
					"Unable to set cancel type");
	if (oldType != newType)
	{
		POSIX_CALL_ASSERT_X((errno = pthread_setcanceltype(int(oldType), NULL)) == 0,
						"Unable to set cancel type");
	}
	return oldType;
}

void Thread::setCancelType( CancelType_t newState ) {
	CancelState_t oldState;

	POSIX_CALL_ASSERT_X((errno = pthread_setcancelstate(int(newState),
			reinterpret_cast<int*>(&oldState))) == 0,
					"Unable to set cancel state" );
}
